package cn.lblbc.lib.download

import android.content.Context
import android.util.Log
import cn.lblbc.lib.download.DownloadError.DEFAULT_ERROR
import cn.lblbc.lib.download.DownloadError.UNKNOWN_HTTP_CODE
import cn.lblbc.lib.download.db.AppDatabase
import cn.lblbc.lib.download.db.bean.DbDownloadPartInfo
import cn.lblbc.lib.download.db.bean.DbDownloadTaskInfo
import cn.lblbc.lib.download.db.dao.DownloadDao
import cn.lblbc.lib.download.db.dao.DownloadPartDao
import kotlinx.coroutines.Job
import java.io.File
import java.io.RandomAccessFile
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong


class DownloadRequest(var context: Context, var fileUrl: String, var taskId: String, var fileName: String, var filePath: String, var fileDownloadListener: FileDownloadListener) {
    private val tag = "DownloadRequest"
    private var downloadJob: Job? = null
    private var isCancelled = false
    lateinit var mTaskInfo: DbDownloadTaskInfo
    private val downloadTotal = AtomicLong(0L)
    private val cancelledFlag = AtomicBoolean(false)
    private val filePartSize = FILE_PART_SIZE
    private val multiThreadCount = MAX_RUNNING_THREAD_COUNT
    private val countDownLatch = CountDownLatch(multiThreadCount)
    var partsQueue = ConcurrentLinkedQueue<DbDownloadPartInfo>()
    private var downloadDao = AppDatabase.getInstance(context).downloadDao
    private var downloadPartDao = AppDatabase.getInstance(context).downloadPartDao
    private var tempFile: File = File("$filePath.tmp")

    init {
        if (tempFile.parentFile?.exists() == false) {
            tempFile.parentFile?.mkdirs()
        }
    }

    fun cancel() {
        downloadJob?.cancel()
        isCancelled = true
    }

    @kotlin.jvm.Throws
    fun download() {
        prepareDownload()
        for (i in 0 until multiThreadCount) {
            val downloadThread = DownloadThread(downloadDao, downloadPartDao, cancelledFlag, countDownLatch, tempFile, fileUrl, mTaskInfo, partsQueue, downloadTotal)
            downloadThread.start()
        }
        while (true) {
            if (isCancelled) {
                break
            }
            downloadDao.update(mTaskInfo)
            fileDownloadListener.onDownloadProgressChange(mTaskInfo.fileSize, mTaskInfo.downloadedSize, 0)

            Thread.sleep(1000L)
            val currentSize = downloadTotal.get()
            val currentTime = System.currentTimeMillis()
            if (countDownLatch.count == 0L) {
                fileDownloadListener.onDownloadProgressChange(mTaskInfo.fileSize, mTaskInfo.downloadedSize, 0)
                if (partsQueue.isEmpty()) {
                    tempFile.renameTo(File(filePath))
                    downloadDao.deleteById(taskId)
                } else {
                    throw DownloadException(DEFAULT_ERROR, UNKNOWN_HTTP_CODE, "")
                }
                break
            }
        }
    }

    /**
     * 下载前准备，主要是为了获取文件长度并进行分片
     */
    private fun prepareDownload() {
        val result = Result()
        val contentLength = getHttpFileContentLength(fileUrl, result)
        if (contentLength == -1L) {
            throw DownloadException(DEFAULT_ERROR, result.code, result.msg)
        }
        val taskInfo = downloadDao.queryById(taskId)
        var downloadSize = 0L
        if (taskInfo == null) {
            mTaskInfo = DbDownloadTaskInfo(id = taskId, url = fileUrl, fileName = fileName, fileSize = contentLength)
            downloadDao.add(mTaskInfo)
            val parts = calcParts(contentLength, filePartSize, taskId)
            partsQueue = convertToQueue(parts)
            downloadPartDao.add(parts)
        } else {
            mTaskInfo = taskInfo
            if (taskInfo.fileSize != contentLength) {
                clearDownloadedFile(taskInfo)
                taskInfo.fileSize = contentLength
                downloadDao.update(taskInfo)
            }
            downloadPartDao.queryByTaskId(taskId)?.let { partsQueue = convertToQueue(it) }
            downloadSize = calcDownloadSize(contentLength, partsQueue)
        }
        downloadTotal.set(downloadSize)
        prepareTempFile(contentLength)
    }

    private fun prepareTempFile(contentLength: Long) {
        fillTempFile(contentLength)
        if (this.tempFile.exists()) {
            if (tempFile.length() == 0L) {
                tempFile.delete()
                fillTempFile(contentLength)
            }
        } else {
            fillTempFile(contentLength)
        }
    }

    private fun clearDownloadedFile(taskInfo: DbDownloadTaskInfo) {
        File(filePath).delete()
        tempFile.delete()
        taskInfo.downloadedSize = 0
    }

    fun isCancelled(): Boolean {
        return isCancelled
    }

    class DownloadThread(
        var downloadDao: DownloadDao,
        var downloadPartDao: DownloadPartDao,
        var cancelledFlag: AtomicBoolean,
        var countDownLatch: CountDownLatch,
        var tempFile: File,
        var fileUrl: String,
        var taskInfo: DbDownloadTaskInfo,
        var partQueue: ConcurrentLinkedQueue<DbDownloadPartInfo>,
        var downloadTotal: AtomicLong
    ) : Thread() {
        private val tag = "DownloadRequest"
        private fun isCancelled(): Boolean {
            return cancelledFlag.get()
        }

        override fun run() {
            var part: DbDownloadPartInfo? = null
            var raf: RandomAccessFile? = null
            try {
                raf = RandomAccessFile(tempFile, "rw")
                while (!isCancelled() && partQueue.poll().also { part = it } != null) {
                    part?.let { download(raf, fileUrl, it) }
                }
            } catch (e: Exception) {
                part?.let {
                    partQueue.add(it)
                }
            } finally {
                closeStream(raf)
                countDownLatch.countDown()
            }
        }

        fun download(raf: RandomAccessFile, fileUrl: String, partInfo: DbDownloadPartInfo): Boolean {
            val isDownloadSuccess = false
            var connection: HttpURLConnection? = null

            try {
                val url = URL(fileUrl)
                connection = url.openConnection() as HttpURLConnection
                setHttpHeaders(connection, partInfo.partStart + partInfo.partWritten, partInfo.partEnd, fileUrl)
                connection.requestMethod = "GET"
                connection.connectTimeout = 8000
                connection.readTimeout = 5000
                val statusCode: Int = connection.responseCode
                if (statusCode == 200 || statusCode == 206) {
                    val inputStream = connection.inputStream
                    raf.seek(partInfo.partStart + partInfo.partWritten)
                    val buf = ByteArray(1024)
                    var len: Int
                    while (inputStream.read(buf).also { len = it } != -1) {
                        raf.write(buf, 0, len)
                        taskInfo.downloadedSize = downloadTotal.addAndGet(len.toLong())
                        partInfo.partWritten = partInfo.partWritten + len
                        downloadPartDao.update(partInfo)
//                    StatusManager.notifyDownloadChanges(packageName, DownloadFileInfo(DownloadStatus.STATUS_DOWNLOADING, fileSize, downloadedSize))
                    }
                    if (isCancelled()) {
                        Log.i(tag, "任务被取消")
                    }
                    if (partInfo.isDownloadComplete()) {
                        downloadPartDao.deleteById(partInfo.id)
                    }

//                    if (downloadTotal.get() == taskInfo.fileSize) {
//                        isDownloadSuccess = true
//                    } else {
////                    StatusManager.notifyDownloadChanges(packageName, DownloadFileInfo(DownloadStatus.STATUS_PAUSE, 0, 0))
//                    }
                    inputStream.close()
                }
            } finally {
                connection?.disconnect()
            }
            return isDownloadSuccess
        }
    }

    private fun fillTempFile(contentLength: Long) {
        val errorMsg = fillFile(tempFile, contentLength)
    }
}


